package org.testfire.realm.control.testfire;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.*;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import org.testfire.realm.control.exception.UpdateException;
import org.testfire.realm.model.entities.User;
import org.testfire.realm.model.entities.testfire.*;

@SuppressWarnings({"unchecked"})
@PersistenceUnit(name = "persistence/TestFireDB")
@Stateless
public class TestFireDBAO
{

    // Inject the lgsrealm entity manager, as defined in the persistence.xml file.
    @PersistenceContext(unitName = "testfire")
    EntityManager em;

    @Resource
    private UserTransaction utx;

    /**
     * Default constructor.
     */
    public TestFireDBAO()
    {
    }

    /**
     * Return the database access object (DBAO) for our database.
     *
     * @return TestFireDBAO The database access object giving us access to m_user data.
     */
    public static TestFireDBAO getInstance()
    {
        FacesContext context = FacesContext.getCurrentInstance();
        TestFireDBAO dbao = null;
        if ( context != null )
        {
            ELContext elContext = context.getELContext();
            ValueExpression ve = context.getApplication().
                getExpressionFactory().createValueExpression( elContext, "#{TestFireDBAO}", Object.class );
            dbao = (TestFireDBAO)ve.getValue( elContext );
        }
        return dbao;
    }

    public static TestFireDBAO lookup() throws NamingException
    {
        Context initCtx = new InitialContext();
        EntityManagerFactory emf = Persistence.createEntityManagerFactory( "testfire" );
        UserTransaction utx = (UserTransaction)initCtx.lookup( "java:comp/UserTransaction" );
        TestFireDBAO dbao = new TestFireDBAO();
        dbao.em = emf.createEntityManager();
        dbao.em.setFlushMode( FlushModeType.AUTO );
        dbao.utx = utx;
        return dbao;
    }

    public EntityManager getEm()
    {
        return em;
    }

    public UserTransaction getUtx()
    {
        return utx;
    }

    /**
     * Merges back any changes to this object.
     *
     * @param obj -
     * @throws UpdateException if the set could not be updated, perhaps due to
     *                         database connection issues or a null entity manager
     *                         (see AbstractUserBean#updateUserEntity for more on this particular error).
     * @return The object which has been merged into this persistence context.
     */
    public <T> T merge( T obj ) throws UpdateException
    {
        T merged = null;
        try
        {
            boolean needTrans = utx.getStatus() == Status.STATUS_NO_TRANSACTION;
            if ( needTrans )
            {
                utx.begin();
            }
            merged = em.merge( obj );
            if ( needTrans )
            {
                utx.commit();
            }
        }
        catch ( Exception ex )
        {
            throw new UpdateException( "Couldn't merge: " + obj + "\n" + ex.getMessage(), ex );
        }
        return merged;
    }

    /**
     * Force the entity manager to push data from its persistence context down to the DB.
     */
    public void flush()
    {
        try
        {
            utx.begin();
            em.flush();
            utx.commit();
        }
        catch ( Exception ex )
        {
            throw new UpdateException( "Couldn't flush to DB" + "\n" + ex.getMessage(), ex );
        }
    }

    /**
     * Force the EntityManager to refresh the data on the passed persistence entity from the database.
     * @param obj The object to refresh from the DB.
     * @throws UpdateException If the object could not be refreshed.
     */
    public void refresh( Object obj ) throws UpdateException
    {
        try
        {
            utx.begin();
            em.refresh( obj );
            utx.commit();
        }
        catch ( Exception ex )
        {
            throw new UpdateException( "Couldn't refresh: " + obj + "\n" + ex.getMessage(), ex );
        }
    }

    /**
     * Merges back any changes to this object.
     *
     * @param obj User
     * @throws UpdateException if the set could not be updated, perhaps due to
     *                         database connection issues or a null entity manager
     *                         (see AbstractUserBean#updateUserEntity for more on this particular error).
     */
    public void persist( Object obj ) throws UpdateException
    {
        try
        {
            utx.begin();
            em.persist( obj );
            em.flush();
            utx.commit();

        }
        catch ( Exception ex )
        {
            throw new UpdateException( "Couldn't merge: " + obj + "\n" + ex.getMessage(), ex );
        }
    }

    /**
     * Get all the testsets owned by User <code>u</code>.
     *
     * @param u The user.
     * @return all the testsets owned by User <code>u</code>.
     */
    public List<TestsetEntity> getTestsets( User u )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(ts) FROM TestsetEntity ts WHERE ts.username = :username" );
        query.setParameter( "username", u.getName() );
        return query.getResultList();
    }

    /**
     * Return all the available TestsetEntity records.
     *
     * @return All of the available TestsetEntity records.
     */
    public List<TestsetEntity> getTestsets()
    {
        Query query = em.createQuery(
            "SELECT OBJECT(ts) FROM TestsetEntity ts " );
        return query.getResultList();
    }

    public TestRunnerQueueEntity getTestRunnerQueueEntity( String queueName )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestRunnerQueueEntity tr WHERE tr.name = :queueName" );
        query.setParameter( "queueName", queueName );
        TestRunnerQueueEntity e = null;
        try
        {
            e = (TestRunnerQueueEntity)query.getSingleResult();
        }
        catch ( Exception e1 )
        {
            e = new TestRunnerQueueEntity();
            e.setName( queueName );
            CrontaskEntity cron = new CrontaskEntity();
            cron.setName( "crontask" );
            e.setCronTask( cron );
            persist( e );
        }
        return e;
    }

    public TestRunnerQueueEntity getTestRunnerQueueEntity( Integer queueId )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestRunnerQueueEntity tr WHERE tr.id = :queueId" );
        query.setParameter( "queueId", queueId );
        TestRunnerQueueEntity e = null;
        e = (TestRunnerQueueEntity)query.getSingleResult();
        return e;
    }

    /**
     * Get a test set from the database by ID, if one exists; null otherwise.
     *
     * @param id The id of the Testset desired.
     * @return The matching Testset, or null.
     */
    public TestsetEntity getTestset( int id )
    {
        try
        {
            Query query = em.createQuery( "SELECT OBJECT(ts) FROM TestsetEntity ts WHERE ts.id = :id" );
            query.setParameter( "id", id );
            return (TestsetEntity)query.getSingleResult();
        }
        catch ( Exception e )
        {
            return null;
        }
    }

    /**
     * Create a new test set; does NOT persist it..
     *
     * @return The new TestsetEntity.
     */
    public TestsetEntity createTestset()
    {
        return new TestsetEntity();
    }

    public List<TestsetEntity> getTestsets( TestRunnerQueueEntity queue )
    {
        return new ArrayList<TestsetEntity>( queue.getTestsets() );
    }

    /**
     * @param queue
     * @return
     */
    public static List<AgentEntity> getAvailableAgents( TestRunnerQueueEntity queue, EntityManager em )
    {
        Query query =
            em.createQuery( "SELECT agent FROM AgentEntity agent WHERE :queue not member of agent.queues" );
        query.setParameter( "queue", queue );
        return query.getResultList();
    }

    public static List<AgentEntity> getAvailableAgents(EntityManager em)
    {
        Query query =
            em.createQuery( "SELECT agent FROM AgentEntity agent WHERE agent.hastestset IS NULL AND agent.id > 0" );
        query.setHint( "toplink.refresh", "true" );
        return query.getResultList();
    }

    /**
     * @param queue
     * @return
     */
    public List<TestsetEntity> getAvailableTestsets( TestRunnerQueueEntity queue )
    {
        Query query =
            em.createQuery( "SELECT ts FROM TestsetEntity ts WHERE :queue not member of ts.queues" );
        query.setParameter( "queue", queue );
        return query.getResultList();
    }

    public List<TestRunnerQueueEntity> getQueues()
    {
        Query query = em.createQuery( "SELECT OBJECT(tr) FROM TestRunnerQueueEntity tr" );
        return query.getResultList();
    }

    public List<TestRunnerQueueEntity> getQueues( User u )
    {
        Query query = em.createQuery( "SELECT OBJECT(tr) FROM TestRunnerQueueEntity tr" );
        return query.getResultList();
    }

    public List<TestRunnerQueueEntity> getQueues( CrontaskEntity task )
    {
        Query query = em.createQuery( "SELECT OBJECT(tr) FROM TestRunnerQueueEntity tr WHERE tr.cronTask = :task" );
        query.setParameter( "task", task );
        return query.getResultList();
    }

    public AgentEntity getAgentByName( String name )
    {
        Query query = em.createQuery( "SELECT OBJECT(ae) FROM AgentEntity ae WHERE ae.name = :name" );
        query.setParameter( "name", name );
        return (AgentEntity)query.getSingleResult();
    }

    public AgentEntity getAgentByHost( String host )
    {
        Query query = em.createQuery( "SELECT OBJECT(ae) FROM AgentEntity ae WHERE ae.host = :host" );
        query.setParameter( "host", host );
        return (AgentEntity)query.getSingleResult();
    }

    public AgentEntity getAgent( Integer id )
    {
        return em.find( AgentEntity.class, id );
    }

    public static Object getValueExpresionValue( String elExpression )
    {
        FacesContext context = FacesContext.getCurrentInstance();
        ELContext elContext = context.getELContext();
        ExpressionFactory fact = context.getApplication().getExpressionFactory();
        ValueExpression ve = fact.createValueExpression( elContext, "#{" + elExpression + "}", Object.class );
        return ve.getValue( elContext );
    }

     public static <T> T getValueExpresionValue( Class<T> clazz)
    {
        String elExpression = clazz.getName();
        elExpression = elExpression.substring( elExpression.lastIndexOf( ".") + 1);
        FacesContext context = FacesContext.getCurrentInstance();
        ELContext elContext = context.getELContext();
        ExpressionFactory fact = context.getApplication().getExpressionFactory();
        ValueExpression ve = fact.createValueExpression( elContext, "#{" + elExpression + "}", Object.class );
        return (T)ve.getValue( elContext );
    }



    public List<CrontaskEntity> getCronTasks()
    {
        Query query = em.createQuery(
            "SELECT OBJECT(task) FROM CrontaskEntity task " );
        return query.getResultList();
    }

    public CrontaskEntity getCronTask( Integer id )
    {
        return em.find( CrontaskEntity.class, id );
    }

    public TestsetResultEntity getTestsetResult( Integer id )
    {
        return em.merge( em.find( TestsetResultEntity.class, id ) );
    }

    public List<TestsetResultEntity> getTestsetResult( TestsetEntity testset, int statuscode )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestsetResultEntity tr WHERE tr.testset = :testset AND tr.statuscode = :statuscode" );
        query.setParameter( "testset", testset );
        query.setParameter( "statuscode", statuscode );
        return query.getResultList();
    }

    public List<TestsetResultEntity> getTestsetResult( AgentEntity agent, TestsetEntity testset, int statuscode )
    {

        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestsetResultEntity tr WHERE tr.agent = :agent AND tr.testset = :testset AND tr.statuscode = :statuscode" );
        query.setParameter( "agent", agent );
        query.setParameter( "testset", testset );
        query.setParameter( "statuscode", statuscode );
        return query.getResultList();
    }

    public List<TestsetResultEntity> getTestsetResult( TestRunnerQueueEntity queue, AgentEntity agent,
        TestsetEntity testset, int statuscode )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestsetResultEntity tr WHERE tr.queue = :queue AND tr.agent = :agent AND tr.testset = :testset AND tr.statuscode = :statuscode" );
        query.setParameter( "queue", queue );
        query.setParameter( "agent", agent );
        query.setParameter( "testset", testset );
        query.setParameter( "statuscode", statuscode );
        return query.getResultList();
    }

    public static List<TestsetResultEntity> getTestsetResultByStatus( int statuscode, EntityManager em )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(tr) FROM TestsetResultEntity tr WHERE tr.statuscode = :statuscode" );
        query.setParameter( "statuscode", statuscode );
        return query.getResultList();
    }

    public TestEntity getTest( Integer testsetId, String testName )
    {
        Query query = em.createQuery(
            "SELECT OBJECT(t) FROM TestEntity t, TestsetEntity ts WHERE ts.id = :testsetId AND t.name = :testName AND t member of ts.tests" );
        query.setParameter( "testsetId", testsetId );
        query.setParameter( "testName", testName );
        TestEntity test = (TestEntity)query.getSingleResult();
        test = em.merge( test );
        return test;
    }
}